package com.heima.article.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.heima.article.mapper.ArticleScoreMapper;
import com.heima.article.mapper.RecommendMapper;
import com.heima.article.service.ApArticleService;
import com.heima.article.service.ArticleRecommendService;
import com.heima.article.service.ArticleScoreService;
import com.heima.model.article.pojos.ApArticle;
import com.heima.model.article.pojos.ArticleUserScore;
import com.heima.model.article.pojos.Recommend;
import com.heima.model.common.dtos.ResponseResult;
import com.heima.utils.thread.AppThreadLocalUtil;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Select;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.ArrayList;
import java.util.List;

/**
 * @ClassName ArticleRecommendServiceImpl
 * @Description TODO
 * @Author Neo
 * @Date 2024/3/9 20:43
 * @Version 1.0
 */
@Component
public class ArticleRecommendServiceImpl extends ServiceImpl<RecommendMapper, Recommend> implements ArticleRecommendService {
    @Autowired
    private ApArticleService apArticleService;
    @Autowired
    private ArticleScoreService articleScoreService;
    @Autowired
    private RecommendMapper recommendMapper;
    static Map<String,Integer> itemIDMap = new HashMap<String,Integer>();//产品ID列表  产品-id
    static Map<Integer,String> idToItemMap = new HashMap<Integer,String>();//产品ID转产品原名称 id-产品
    static Map<String, HashMap<String, Double>> itemMap = new HashMap<>(); //针对每个产品，存储所有用户对该产品的评分

    static Map<String,Integer> userIDMap = new HashMap<String,Integer>();//用户ID列表
    static Map<Integer,String> idToUserMap = new HashMap<Integer,String>();//用户ID转用户原名称
    static Map<String,HashMap<String,Double>> userMap = new HashMap<String,HashMap<String, Double>>(); //针对每个用户，记录用户对于产品的评分
    static double[][] simMatrix; //产品之间的相似矩阵
    static int TOP_K = 25;  //选择的相似item的数量
    static int TOP_N = 6;  //定义最长推荐列表
    //读取用户UI交互
    public void readUI() {
        ResponseResult<List<ArticleUserScore>> scoreData = articleScoreService.getScoreData(null);
        List<ArticleUserScore> data = scoreData.getData();


        String line;
        String[] SplitLine=new String[3];

        int itemId = 0; //产品计数
        int userId = 0;//用户计数
        for (ArticleUserScore datum : data) {
            SplitLine[0]=datum.getUserId().toString();
            SplitLine[1]=datum.getArticleId().toString();
            SplitLine[2]=datum.getScore().toString();

            //如果不包含当前产品，存入产品map以及产品idmap中
            if(!itemIDMap.containsKey(SplitLine[1])) {
                HashMap<String, Double> currentUserMap = new HashMap<>();//存入当前的用户评分
                currentUserMap.put(SplitLine[0], Double.parseDouble(SplitLine[2]));	//用户-评分
                itemMap.put(SplitLine[1], currentUserMap); //在itemMap中存入产品-评分
                itemIDMap.put(SplitLine[1], itemId);
                idToItemMap.put(itemId, SplitLine[1]);
                itemId ++;
            }else {  //如果已经存在，进行Map更新
                HashMap<String, Double> currentUserMap = itemMap.get(SplitLine[1]); //获取已有产品所包含的评分
                currentUserMap.put(SplitLine[0], Double.parseDouble(SplitLine[2]));//加入新的用户-评分
                itemMap.put(SplitLine[1], currentUserMap);
            }
            //如果不包含当前的用户，存入map中
            if(!userMap.containsKey(SplitLine[0])) {

                userIDMap.put(SplitLine[0], userId);
                idToUserMap.put(userId, SplitLine[0]);

                userId++;
                //新建Map用于存储当前用户的评分列表
                HashMap<String, Double> curentUserMap = new HashMap<String,Double>();
                //将当前用户评分加入当前评分列表中
                curentUserMap.put(SplitLine[1], Double.parseDouble(SplitLine[2]));
                userMap.put(SplitLine[0], curentUserMap);
            }else { //如果已存在当前用户，将该用户先前的评分拿出来，再加入新的评分
                HashMap<String, Double> curentUserMap = userMap.get(SplitLine[0]);
                curentUserMap.put(SplitLine[1], Double.parseDouble(SplitLine[2]));
                userMap.put(SplitLine[0], curentUserMap);
            }
        }

    }
    //获取产品之间的相似性
    public  void item_similarity() {
        //初始化用户相似矩阵
        simMatrix = new double[itemMap.size()][itemMap.size()];
        int itemCount = 0;
        //循环每个产品计算相似性:Jaccard 相似性
        for(Map.Entry<String, HashMap<String, Double>> itemEntry_1 : itemMap.entrySet()) {
            System.out.println("计算"+itemCount);
            //获取为当前产品评分的所有用户
            Set<String> ratedUserSet_1 = new HashSet<>();
            for(Map.Entry<String, Double> userEntry : itemEntry_1.getValue().entrySet()) {
                //将已评分用户存入set集合中
                ratedUserSet_1.add(userEntry.getKey());
            }

            int ratedUserSize_1 = ratedUserSet_1.size();//第一个产品所有评论数

            //循环其他产品
            for(Map.Entry<String, HashMap<String, Double>> itemEntry_2 : itemMap.entrySet()) {
                //首先判断第二个产品的id是否大于第一个，是的话再进行计算，避免重复计算
                if(itemIDMap.get(itemEntry_2.getKey())>itemIDMap.get(itemEntry_1.getKey())) {
                    //同样获取为当前产品评分的所有用户
                    Set<String> ratedUserSet_2 = new HashSet<>();
                    for(Map.Entry<String, Double> userEntry : itemEntry_2.getValue().entrySet()) {
                        ratedUserSet_2.add(userEntry.getKey());
                    }
                    //通过jaccard相似度计算产品相似度

                    int ratedUserSize_2 = ratedUserSet_2.size();//第二个产品所有评论数
                    int sameUerSize = interCount(ratedUserSet_1,ratedUserSet_2); //取两个集合的交集的数量

                    double similarity = sameUerSize/(Math.sqrt(ratedUserSize_1*ratedUserSize_2));
                    //把相似性存入相似矩阵中
                    simMatrix[itemIDMap.get(itemEntry_1.getKey())][itemIDMap.get(itemEntry_2.getKey())] = similarity;
                    simMatrix[itemIDMap.get(itemEntry_2.getKey())][itemIDMap.get(itemEntry_1.getKey())] = similarity;
                }
            }
            itemCount++;

//			for (int i = 0; i < simMatrix.length; i++) {
//				for (int j = 0; j < simMatrix.length; j++) {
//					System.out.print(simMatrix[i][j]+" ");
//				}
//				System.out.println();
//			}
        }
    }
    //根据产品的相似性进行推荐
    public  void recommend() throws IOException{

        StringBuilder stringBuilder=new StringBuilder();
        //根据item相似度获取每个item最相似的TOP_K个产品
        Map<Integer, HashSet<Integer>> nearestItemMap = new HashMap<>();

        for(int i = 0;i<itemMap.size();i++) {
            Map<Integer, Double> simMap = new HashMap<>();
            for(int j = 0;j<itemMap.size();j++) {
                simMap.put(j,simMatrix[i][j]);
            }

            //对产品相似性进行排序
            simMap = sortMapByValues(simMap);

            int simItemCount = 0;
            HashSet<Integer> nearestItemSet = new HashSet<>();
            for(Map.Entry<Integer, Double> entry : simMap.entrySet()) {
                if(simItemCount<TOP_K) {
                    nearestItemSet.add(entry.getKey()); //获取相似itemID存入集合中
                    simItemCount++;
                }else
                    break;
            }
            //相似物品结果存入map中
            nearestItemMap.put(i,nearestItemSet);
        }

        //循环每个用户，循环每个产品,计算用户对没有买过的产品的打分，取TOP_N得分最高的产品进行推荐
        for(int i = 0;i<userMap.size();i++) {
            System.out.println("为用户"+i+"推荐");
            //获取当前用户所有评论过的产品
            HashSet<Integer> currentUserSet = new HashSet<>();
            Map<String,Double> preRatingMap = new HashMap<String,Double>();

            for(Map.Entry<String, Double> entry :userMap.get(idToUserMap.get(i)).entrySet()) {
                currentUserSet.add(itemIDMap.get(entry.getKey())); //将该用户评论过的产品以产品id的形式存入集合中
            }

            //循环每个产品
            for(int j = 0;j<itemMap.size();j++) {
                double preRating = 0;
                double sumSim = 0;

                //首先判断用户购买的列表中是否包含当前商品，如果包含直接跳过
                if(currentUserSet.contains(j))
                    continue;

                //判断当前产品的近邻中是否包含这个产品
                Set<Integer> interSet = interSet(currentUserSet, nearestItemMap.get(j));//获取当前用户的购买列表与产品相似品的交集

                //如果交集为空，则该产品预测评分为0
                if(!interSet.isEmpty()) {
                    for(int item :interSet) {
                        sumSim += simMatrix[j][item];
                        preRating += simMatrix[j][item]* userMap.get(idToUserMap.get(i)).get(idToItemMap.get(item));

                    }

                    if(sumSim != 0) {
                        preRating = preRating/sumSim; //如果相似性之和不为0计算得分，否则得分为0
                    }else
                        preRating = 0;
                }else  //如果交集为空的话，直接评分为0
                    preRating = 0;
                preRatingMap.put(idToItemMap.get(j), preRating);
            }
            preRatingMap = sortMapByValues(preRatingMap);

            if(!preRatingMap.isEmpty()) {
                stringBuilder.append(idToUserMap.get(i)+":");
            }

            //推荐TOP_N个产品
            int recCount = 0;
            int recCountSQL= 0;
            for(Map.Entry<String, Double> entry : preRatingMap.entrySet()) {
                if(recCount < TOP_N) {
                    stringBuilder.append(entry.getKey() + " ");
                    recCount ++;
                }
            }
            LambdaQueryWrapper<Recommend> wrapper = new LambdaQueryWrapper<>();
            wrapper.eq(Recommend::getUserId,idToUserMap.get(i));
            Recommend one = getOne(wrapper);
            if(one==null){
                StringBuilder res = new StringBuilder();
                Recommend recommend = new Recommend();
                recommend.setUserId(Long.valueOf(idToUserMap.get(i)));
                for(Map.Entry<String, Double> entry : preRatingMap.entrySet()) {
                    if(recCountSQL < TOP_N) {
                        if(StringUtils.isNotBlank(res)){
                            res.append(",");
                        }
                        res.append(entry.getKey());
                        recCountSQL++;
                    }
                }
                recommend.setArticleId(res.toString());
                recommendMapper.insert(recommend);
            }else{
                StringBuilder res = new StringBuilder();
                for(Map.Entry<String, Double> entry : preRatingMap.entrySet()) {
                    if(recCountSQL < TOP_N) {
                        if(StringUtils.isNotBlank(res)){
                            res.append(",");
                        }
                        res.append(entry.getKey());
                        recCountSQL++;
                    }
                }
                one.setArticleId(res.toString());
                recommendMapper.updateById(one);
            }
        }
        System.out.println(stringBuilder.toString());
    }
    //求两个集合交集
    public  int interCount(Set<String> set_a,Set<String> set_b) {
        int samObj = 0;
        for(Object obj:set_a) {
            if(set_b.contains(obj))
                samObj++;
        }
        return samObj;
    }
    //求两个集合交集的数量
    public  Set<Integer> interSet(Set<Integer> set_a,Set<Integer> set_b) {
        Set<Integer> tempSet = new HashSet<>();
        for(Object obj:set_a) {
            if(set_b.contains(obj))
                tempSet.add((Integer) obj);
        }
        return tempSet;
    }

    //对map进行从大到小排序
    public  <K extends Comparable, V extends Comparable> Map<K, V> sortMapByValues(Map<K, V> aMap) {
        HashMap<K, V> finalOut = new LinkedHashMap<>();
        aMap.entrySet().stream().sorted((p1, p2) -> p2.getValue().compareTo(p1.getValue())).collect(Collectors.toList())
                .forEach(ele -> finalOut.put(ele.getKey(), ele.getValue()));
        return finalOut;
    }


    @Override
    public ResponseResult getRecommend(Long userId) {

        QueryWrapper<Recommend> recommendQueryWrapper = new QueryWrapper<>();
        recommendQueryWrapper.eq("userId",userId);
        Recommend recommend = getOne(recommendQueryWrapper);
        ArrayList<Long> articleIds = new ArrayList<>();
        if(recommend!=null&&recommend.getArticleId()!=null){
            String articleId = recommend.getArticleId();
            String[] split = articleId.split(",");
            for (String s : split) {
                Long id = Long.valueOf(s);
                articleIds.add(id);
            }
            if(!CollectionUtils.isEmpty(articleIds)){
                List<ApArticle> apArticles = apArticleService.listByIds(articleIds);
                apArticles.removeIf(apArticle -> {
                    return 4==apArticle.getFlag();
                });
                return ResponseResult.okResult(apArticles);
            }
        }else{
            QueryWrapper<ApArticle> apArticleQueryWrapper = new QueryWrapper<>();
            apArticleQueryWrapper.last("order by rand() LIMIT 6");
            List<ApArticle> list = apArticleService.list(apArticleQueryWrapper);
            return ResponseResult.okResult(list);
        }
        return null;
    }

    @Override
    public ResponseResult calRecommend(Long userId) {
        readUI();
        item_similarity();
        try {
            recommend();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
}




